Conversation
…ovements, and ambiguous method detection
…desState for .NET parity
client/src/test/java/com/microsoft/durabletask/EntityQueryPageableTest.java
Fixed
Show fixed
Hide fixed
client/src/test/java/com/microsoft/durabletask/EntityQueryPageableTest.java
Fixed
Show fixed
Hide fixed
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationContext.java
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment.
Pull request overview
Adds Durable Entities support to the Java SDK, including worker-side entity execution, orchestration↔entity APIs, client management/query APIs, Azure Functions integration, and samples/tests demonstrating the feature set.
Changes:
- Add core entity programming model APIs (entities, entity context/state/operation abstractions, entity client, options, query/paging, storage cleanup).
- Integrate entity messaging + locking into orchestration execution and worker-side work-item processing.
- Add Azure Functions entity trigger + middleware, plus multiple new samples and extensive unit/integration tests.
Reviewed changes
Copilot reviewed 56 out of 56 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| samples/src/main/java/io/durabletask/samples/LowLevelEntitySample.java | Sample: low-level entity + state dispatch + implicit delete |
| samples/src/main/java/io/durabletask/samples/EntityTimeoutSample.java | Sample: callEntity timeouts + scheduled signals |
| samples/src/main/java/io/durabletask/samples/EntityQuerySample.java | Sample: entity query + storage cleanup |
| samples/src/main/java/io/durabletask/samples/EntityCommunicationSample.java | Sample: entity→entity signaling + entity-started orchestrations |
| samples/src/main/java/io/durabletask/samples/CounterEntitySample.java | Sample: basic counter entity usage |
| samples/src/main/java/io/durabletask/samples/BankAccountSample.java | Sample: entity locking + atomic transfer orchestration |
| internal/durabletask-protobuf/protos/orchestrator_service.proto | Proto updates for worker request filtering |
| internal/durabletask-protobuf/PROTO_SOURCE_COMMIT_HASH | Tracks upstream proto sync commit |
| client/src/test/java/com/microsoft/durabletask/TypedEntityMetadataTest.java | Tests for typed entity metadata wrapper |
| client/src/test/java/com/microsoft/durabletask/TaskEntityTest.java | Tests for reflection dispatch, implicit delete, state dispatch behavior |
| client/src/test/java/com/microsoft/durabletask/TaskEntityExecutorTest.java | Tests for batch execution semantics, rollback, actions |
| client/src/test/java/com/microsoft/durabletask/IntegrationTestBase.java | Test worker builder support for entity registration |
| client/src/test/java/com/microsoft/durabletask/EntityRegistrationTest.java | Tests for worker builder addEntity overloads |
| client/src/test/java/com/microsoft/durabletask/EntityQueryTest.java | Tests for query normalization/defaults/chaining |
| client/src/test/java/com/microsoft/durabletask/EntityQueryResultTest.java | Tests for query result container |
| client/src/test/java/com/microsoft/durabletask/EntityQueryPageableTest.java | Tests for auto-pagination behavior |
| client/src/test/java/com/microsoft/durabletask/EntityOptionsTest.java | Tests for signal/call entity options |
| client/src/test/java/com/microsoft/durabletask/EntityMetadataTest.java | Tests for entity metadata parsing/state deserialization |
| client/src/test/java/com/microsoft/durabletask/EntityIntegrationTests.java | Sidecar-backed integration tests for entities |
| client/src/test/java/com/microsoft/durabletask/EntityInstanceIdTest.java | Tests for entity ID parsing/format/ordering |
| client/src/test/java/com/microsoft/durabletask/CleanEntityStorageResultTest.java | Tests for cleanup result model |
| client/src/test/java/com/microsoft/durabletask/CleanEntityStorageRequestTest.java | Tests for cleanup request model |
| client/src/main/java/com/microsoft/durabletask/TypedEntityMetadata.java | Typed state wrapper for entity metadata |
| client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java | Orchestrator integration: entity signaling/calling/locking + event handling |
| client/src/main/java/com/microsoft/durabletask/TaskOrchestrationContext.java | Public orchestration APIs for entity operations + locking |
| client/src/main/java/com/microsoft/durabletask/TaskEntityState.java | Entity state wrapper with commit/rollback semantics |
| client/src/main/java/com/microsoft/durabletask/TaskEntityOperation.java | Entity operation input/state/context container |
| client/src/main/java/com/microsoft/durabletask/TaskEntityFactory.java | Functional factory for entity instantiation |
| client/src/main/java/com/microsoft/durabletask/TaskEntityExecutor.java | Worker-side executor for entity batches + transactional semantics |
| client/src/main/java/com/microsoft/durabletask/TaskEntityContext.java | Entity context for signaling/starting orchestrations |
| client/src/main/java/com/microsoft/durabletask/TaskEntity.java | Reflection-based entity dispatch base class |
| client/src/main/java/com/microsoft/durabletask/SignalEntityOptions.java | Options for scheduled entity signals |
| client/src/main/java/com/microsoft/durabletask/ITaskEntity.java | Entity interface contract |
| client/src/main/java/com/microsoft/durabletask/GrpcDurableEntityClient.java | gRPC implementation of DurableEntityClient management APIs |
| client/src/main/java/com/microsoft/durabletask/EntityRunner.java | Helper for executing entity batches from encoded protobuf payloads |
| client/src/main/java/com/microsoft/durabletask/EntityQueryResult.java | Entity query result model |
| client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java | Auto-pagination support for entity queries |
| client/src/main/java/com/microsoft/durabletask/EntityQuery.java | Entity query model + prefix normalization |
| client/src/main/java/com/microsoft/durabletask/EntityOperationFailedException.java | Exception type for failed two-way entity calls |
| client/src/main/java/com/microsoft/durabletask/EntityMetadata.java | Entity metadata model + state deserialization |
| client/src/main/java/com/microsoft/durabletask/EntityInstanceId.java | Entity instance identifier type and parsing |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilder.java | Worker builder support for registering entities + concurrency option |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java | Worker runtime support for entity work items |
| client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java | Expose entity client from gRPC client |
| client/src/main/java/com/microsoft/durabletask/DurableTaskClient.java | Add entity API surface + deprecated forwarding helpers |
| client/src/main/java/com/microsoft/durabletask/DurableEntityClient.java | Durable entity management client abstraction |
| client/src/main/java/com/microsoft/durabletask/CleanEntityStorageResult.java | Model for cleanup results |
| client/src/main/java/com/microsoft/durabletask/CleanEntityStorageRequest.java | Model for cleanup requests |
| client/src/main/java/com/microsoft/durabletask/CallEntityOptions.java | Options for callEntity timeouts |
| client/build.gradle | Windows-friendly java/javac path handling for tests |
| azuremanaged/build.gradle | Windows-friendly javac path handling for tests |
| azurefunctions/src/main/resources/META-INF/services/com.microsoft.azure.functions.internal.spi.middleware.Middleware | Register entity middleware |
| azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/internal/middleware/EntityMiddleware.java | Azure Functions middleware for entity triggers |
| azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableEntityTrigger.java | Azure Functions entity trigger annotation |
| azurefunctions/src/main/java/com/microsoft/durabletask/azurefunctions/DurableClientContext.java | Entity management APIs surfaced via DurableClientContext |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…letask-java into vabachu/entities
client/src/test/java/com/microsoft/durabletask/TaskOrchestrationEntityEventTest.java
Fixed
Show fixed
Hide fixed
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationContext.java
Fixed
Show fixed
Hide fixed
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationEntityFeature.java
Fixed
Show fixed
Hide fixed
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationContext.java
Fixed
Show fixed
Hide fixed
YunchuWang
left a comment
There was a problem hiding this comment.
Code Review: Durable Entities for Java SDK
Thorough review of the 78-file PR adding Durable Entities support. The overall design is solid and well-documented, with good alignment to the .NET SDK patterns. However, I found several issues ranging from a Java 8 compatibility breakage to unused concurrency controls and excessive logging. Details in inline comments below.
Summary of Findings
Critical (must fix):
Class.getPackageName()breaks Java 8 compatibility (project targets Java 8)maxConcurrentEntityWorkItemsis accepted but never enforced- Activity execution silently changed from synchronous to unbounded-concurrent
Medium:
4. EntityMetadata.includesState incorrectly derived from proto field presence
5. EntityQueryPageable.cloneQuery() duplicates EntityQuery.copy() and double-normalizes
6. TaskEntityOperation constructor should be package-private
Low / Code Quality:
7. INFO-level logging on orchestration replay hot path will flood production logs
8. Inconsistent indentation in ENTITYREQUESTV2 handler and findMethodUncached
9. Field declared after methods that reference it in CleanEntityStorageRequest
10. Missing newline at end of several files
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/GrpcDurableEntityClient.java
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/EntityQueryPageable.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/TaskEntityOperation.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/CleanEntityStorageRequest.java
Outdated
Show resolved
Hide resolved
YunchuWang
left a comment
There was a problem hiding this comment.
🔍 Strict Code Review — Durable Entities (Java SDK)
This is a thorough review of the 12,394-line, 78-file Durable Entities feature addition. The overall architecture is solid and well-aligned with the .NET SDK design. However, there are several correctness, safety, and API-design issues that should be addressed before merge.
I've organized findings into tiers — please prioritize the Critical/High items.
🔴 Critical / High Priority
1. Lock release close() is not idempotent — can cause non-deterministic replay (TaskOrchestrationExecutor.java)
The AutoCloseable returned from lockEntities() sends unlock messages and increments sequenceNumber every time close() is called. If a user calls close() twice (very common with try-with-resources + explicit close, or exception handling double-close), this will: (a) send duplicate unlock messages corrupting entity state, and (b) increment sequenceNumber again, causing non-deterministic replay failures on subsequent executions. Fix: Add a boolean guard to make close() idempotent.
2. Critical section enforcement bypassed when lockedEntityIds is null (TaskOrchestrationExecutor.java)
In callEntity():
if (this.isInCriticalSection && this.lockedEntityIds != null && !this.lockedEntityIds.contains(...))handleEntityLockGranted() sets this.lockedEntityIds = this.pendingLockSets.remove(criticalSectionId), which returns null if the key is missing. Combined with isInCriticalSection = true, the null short-circuits the safety check, allowing calls to any entity — defeating deadlock prevention. Fix: Throw if pendingLockSets.remove() returns null.
3. Activity completion failures silently lost (DurableTaskGrpcWorker.java)
Activities are now dispatched to workItemExecutor, but sidecarClient.completeActivityTask() inside the Runnable has no outer try-catch for the gRPC call itself. If the gRPC call fails (network issue, sidecar crash), the exception hits only the thread pool's uncaught exception handler — the activity result is permanently lost with no retry or abandon. Entity work items have an abandon fallback, but activities do not.
4. setState() silently deletes entity state on serialization failure (TaskEntityState.java)
this.serializedState = this.dataConverter.serialize(state);
if (this.serializedState == null) {
deleteState(); // <-- silent data loss!
}Since the outer if already guarantees state != null, serialize() returning null means a broken DataConverter. Silently calling deleteState() is a data-loss bug. Should throw IllegalStateException.
5. Unbounded thread pool (DurableTaskGrpcWorker.java)
Executors.newCachedThreadPool() has no upper bound. Despite maxConcurrentEntityWorkItems being sent to the sidecar, the local thread pool is unbounded. Activities AND entities both use this pool. A burst of work items can create unbounded threads. Use a bounded ThreadPoolExecutor or separate pools.
6. Activity execution model silently changed from sync to async (DurableTaskGrpcWorker.java)
Activities were previously executed synchronously on the main dispatch thread. Moving them to the thread pool is a silent behavioral breaking change: (a) activities using ThreadLocal state will break, (b) activities relying on sequential execution relative to dispatch will break, (c) gRPC context propagation may be lost. This deserves a CHANGELOG entry.
7. gRPC exceptions not wrapped in GrpcDurableEntityClient.java
Existing DurableTaskGrpcClient methods catch StatusRuntimeException and translate to domain exceptions. All new entity gRPC calls let raw gRPC exceptions escape. This breaks the DurableEntityClient abstraction — callers shouldn't need to catch gRPC types.
8. Excessive INFO-level logging will flood production logs (TaskOrchestrationExecutor.java)
Multiple log statements changed from FINE to INFO: every orchestrator yield, every entity call/response, every new event processed, every buffered event. In production with hundreds of orchestrations, this will flood logs. Most should remain at FINE/DEBUG.
🟡 Medium Priority
9. getCommittedActions() iterates ALL pending actions, not committed count (TaskEntityExecutor.java)
The method iterates all pendingActions rather than bounding by committedActionCount. It relies on a fragile invariant that both are equal at call time. If that invariant is ever violated (e.g., future refactoring), uncommitted actions leak into the result. Bound the loop by committedActionCount.
10. resultNode.asText() loses data for non-string JSON nodes (TaskOrchestrationExecutor.java)
JsonNode.asText() returns "" for object/array nodes. If the DTFx ResponseMessage "result" field ever contains a raw JSON object, the result is silently lost. Use resultNode.isTextual() ? resultNode.asText() : resultNode.toString().
11. Legacy callEntity path omits "signal" field
Legacy signalEntity JSON explicitly sets "signal": true, but callEntity does not set "signal": false. If DTFx defaults absence to true, all calls would be misrouted as fire-and-forget signals.
12. EntityQueryPageable — hasNext() triggers network calls (side-effect violation)
Both the constructor and hasNext() eagerly call fetchNextPage(), which executes gRPC queries. The Iterator.hasNext() contract should be a pure observation. Defer first page fetch to first hasNext() call.
13. TypedEntityMetadata eagerly deserializes — single bad entity poisons entire query
If readStateAs() throws for one entity, TypedEntityMetadata construction fails, halting iteration over the entire result set. No way to skip corrupt entities.
14. Javadoc says "excluding state" but behavior includes state (DurableTaskClient.java)
The deprecated getEntityMetadata(entityId) doc says "excluding its state" but delegates to getEntities().getEntityMetadata(entityId) which defaults to includeState = true.
15. EntityRunner silently swallows malformed instance IDs
try {
entityName = EntityInstanceId.fromString(instanceId).getName();
} catch (Exception e) {
entityName = instanceId; // Silent fallback
}This masks the real problem and produces a confusing "No entity named '...' is registered" error later.
16. EntityProxy.getRawClass() doesn't handle WildcardType
Task<? extends Foo> silently falls back to Object.class, losing the upper-bound type.
17. EntityQueryResult.entities list not defensively copied
The constructor stores the list reference directly. Callers can mutate via getEntities().add(...). Use Collections.unmodifiableList.
18. Missing interrupt flag restoration in close() (DurableTaskGrpcWorker.java)
When InterruptedException is caught, Thread.currentThread().interrupt() is not called, violating Java's interrupt contract.
19. operationInfos/results size mismatch silently truncated (DurableTaskGrpcWorker.java, V2 entity path)
If sizes differ, excess infos are quietly dropped or results have no routing info. Neither case is logged. Can cause silent data loss or mis-routed responses.
20. Lazy cachedEntityInstanceId without volatile (EntityMetadata.java)
The field is lazily initialized in getEntityInstanceId(). Without volatile, it's not safe for cross-thread visibility under the Java Memory Model.
🟠 Low Priority / Design
21. ITaskEntity.runAsync() is synchronous — The method returns Object, not CompletableFuture. The Async suffix is misleading per universal Java convention. Rename to run().
22. ITaskEntity uses I prefix — Inconsistent with existing SDK naming (DataConverter, TaskOrchestrationContext).
23. TaskOrchestrationEntityFeature.lockEntities(EntityInstanceId...) is needlessly abstract — Should be a concrete convenience method delegating to the List overload.
24. getEntities() allocates a new object on every call — The .NET SDK caches this. Cache or document.
25. currentCriticalSectionId is set but never read — Dead state. Remove or use.
26. lockEntities doesn't validate duplicate entity IDs or null elements — Same ID twice = double-lock. Null element = cryptic NPE from Collections.sort.
27. No timeout mechanism for lockEntities — Unlike callEntity with CallEntityOptions.getTimeout(), locks can block forever.
28. Worker threads all share the same name "durabletask-worker" — Thread dumps become useless. Use a counter.
29. EntityQuery.setInstanceIdStartsWith("@") produces degenerate prefix — Matches ALL entities.
30. EntityQueryPageable.cloneQuery() duplicates EntityQuery.copy() logic — Maintenance hazard if a field is added.
31. TaskEntityOperation and TaskEntityState constructors are public — These are internal types that users should never construct. Make package-private.
32. cleanEntityStorage in GrpcDurableEntityClient has no max-iteration guard — If the sidecar has a bug returning non-null tokens forever, this is an unbounded blocking call.
PR Checklist
⚠️ CHANGELOG.md is NOT updated (PR checklist item unchecked)⚠️ Merge state is "dirty" — rebase needed- ✅ Extensive unit tests added
- ✅ E2E tests added
Questions for @bachuv
-
Re: #1 (lock close): Is there a reason the lock release wasn't guarded with a boolean flag?
try (AutoCloseable lock = ...)makes double-close very likely. -
Re: #6 (activity model change): Was the sync→async activity execution change intentional? This is a behavioral breaking change that should at minimum be in the CHANGELOG.
-
Re: #8 (logging): Were the
FINE → INFOlogging changes intentional for debugging, or should they be reverted before merge? -
Re: #11 (missing "signal": false): Can you confirm DTFx extension behavior when the
"signal"field is absent from the RequestMessage JSON? -
Re: EVENTSENT uncommented:
handleEventSent()now removes pending actions for ALLEventSentevents, not just entity-related ones. Has this been regression-tested against existing orchestrations usingsendEvent? -
Re: #27 (lock timeout): Is lock timeout deferred to a follow-up? The .NET SDK has this capability.
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java
Outdated
Show resolved
Hide resolved
|
First set of feedback: Critical (must fix):
Medium: Low / Code Quality: Second set of feedback: 🔴 Critical / High Priority
🟡 Medium Priority 🟠 Low Priority / Design Questions Re: #1 (lock close): This was an oversight. The boolean guard has now been added. Double-close is idempotent: first call emits unlocks, subsequent calls are no-ops. Re: #6 (activity model change): This has been reverted so activities are back to synchronous execution on the dispatch thread, identical to main. No behavioral change, no CHANGELOG entry needed. The thread pool is now used exclusively for entity work items. Re: #8 (logging): The FINE → INFO changes were not intentional for production use. They were debug-time changes that should have been reverted. All hot-path logs have been restored to FINE. Only entity operation failures remain at INFO since those are genuinely noteworthy. Re: #11 (missing "signal": false): DTFx RequestMessage.IsSignal is a C# bool property with [DataMember(Name = "signal", EmitDefaultValue = false)]. It defaults to false, and EmitDefaultValue = false means the .NET serializer omits the field when the value is false. So absent and false are functionally identical. DTFx treats both as "this is a call, not a signal." We now explicitly set "signal": false for clarity. Re: EVENTSENT uncommented: Could you point to the specific lines? I want to verify the exact change before answering. The handleEventSent handler should only process entity-related events. If it's broader than that, it needs to be scoped. Re: #27 (lock timeout): Yes, deferred. The .NET SDK's LockEntitiesAsync does not have a timeout parameter. It returns Task with no timeout overload. This would be a new feature for both SDKs, not a parity gap. |
client/src/main/java/com/microsoft/durabletask/AbstractTaskEntity.java
Dismissed
Show dismissed
Hide dismissed
Review Finding: Missing
|
client/src/test/java/com/microsoft/durabletask/EntityIntegrationTests.java
Dismissed
Show dismissed
Hide dismissed
YunchuWang
left a comment
There was a problem hiding this comment.
✅ Approved — Durable Entity Parity Review
Disclosure: This review was generated by AI (GitHub Copilot CLI, Claude Opus 4.6) and approved/granted by reviewer @YunchuWang. The AI performed a systematic cross-SDK parity analysis reading all 77 changed files in this PR against the .NET reference implementation in microsoft/durabletask-dotnet. The reviewer verified the findings and approves this PR.
Verdict: Mostly aligned with minor gaps — safe to merge
This PR delivers a comprehensive Durable Entity implementation for the Java SDK that covers all major feature categories present in the .NET reference. The behavioral model (transactional batch execution, per-operation commit/rollback of state AND actions, signal vs call semantics, entity locking/critical sections) is correctly implemented and well-tested.
Strengths:
- ✅ Core entity model (interface + abstract base + reflection dispatch) fully aligned with .NET
- ✅ All .NET entity client capabilities present (signal, query, metadata, cleanup)
- ✅ Azure Functions integration complete (trigger, middleware, client context)
- ✅ Entity locking with
AutoCloseablecorrectly maps .NET'sIAsyncDisposable - ✅ Test suite (182+ tests) significantly exceeds .NET reference test coverage (~47 abstraction tests)
- ✅ 5 E2E integration tests validate real workflows
- ✅ Non-determinism detection tested for signal/call/lock operations
- ✅ Useful Java-only additions:
CallEntityOptions.timeout,AbstractTaskEntity.dispatch()re-entrant calls
Suggested Follow-Up Issues
The following items are minor and should not block merge, but would improve parity and robustness:
1. EntityMetadata.readStateAs() should guard when state not included (Medium)
.NET's EntityMetadata<T>.State throws when IncludesState=false, protecting against silent bugs where callers forget to request state inclusion. Java's readStateAs() silently returns null, making "state doesn't exist" indistinguishable from "state wasn't fetched". Suggest throwing IllegalStateException when includesState == false.
2. Verify distributed trace context propagation in GrpcDurableEntityClient (Medium)
.NET's GrpcDurableEntityClient.SignalEntityAsync explicitly captures Activity.Current and propagates trace context in gRPC calls. Please verify Java's implementation does the same for observability parity.
3. Verify lock ordering in lockEntities() to prevent deadlocks (Medium)
.NET sorts entity IDs before acquiring locks to prevent deadlocks when multiple orchestrations lock overlapping entity sets. Verify the Java implementation applies the same sorting.
4. Add getState(Class<T>, T defaultValue) overload to TaskEntityState (Low)
.NET offers GetState<T>(T? defaultValue = default) for convenience. Java's getState(Class<T>) always returns null when no state exists. Adding a defaultValue overload improves ergonomics.
5. Document Java-specific additions for cross-SDK awareness (Low)
CallEntityOptions.timeout (with timer-based implementation) and AbstractTaskEntity.dispatch() (re-entrant self-calls) are useful additions not present in .NET. These should be documented as Java-specific features for future cross-SDK alignment discussions.
6. Add EnableEntitySupport configuration option (Low)
.NET's DurableTaskClientOptions.EnableEntitySupport allows disabling entity client creation. Adding a similar toggle to Java would provide configuration parity for environments that don't use entities.
Language-Justified Differences (NOT issues)
These differences are expected and correct:
ValueTask<object?>→ synchronousObjectreturn (Java entity execution model)CancellationToken→ not present (Java uses thread interruption)InCriticalSection(out list)→isInCriticalSection()+getLockedEntities()(nooutparams in Java)- Source generators → JDK dynamic proxy for typed entity proxies
- Immutable records → mutable builder-style options classes
IAsyncDisposable→AutoCloseablefor lock release
LGTM 🚀
Issue describing the changes in this PR
Adding Durable Entities support for the Java SDK.
resolves #31
Pull request checklist
CHANGELOG.md